


#include <stdio.h>
#include <getopt.h>
#include "qelib.h"
#include "libfdt.h"



#define FDT_LOGNAME      "fdt"
#define fdt_debug(...)   qelog_debug(FDT_LOGNAME,   __VA_ARGS__)
#define fdt_info(...)    qelog_info(FDT_LOGNAME,    __VA_ARGS__)
#define fdt_notice(...)  qelog_notice(FDT_LOGNAME,  __VA_ARGS__)
#define fdt_error(...)   qelog_error(FDT_LOGNAME,   __VA_ARGS__)



typedef enum {
    FDT_MODE_DUMP = 0,
    FDT_MODE_COMP,
} fdt_mode_e;


qe_ret fdt_read(const char *file, void **rp)
{
    int size;
    FILE *fp;
    void *buf;

    fp = fopen(file, "rb");
    if (!fp) {
        return qe_err_param;
    }

    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    buf = qe_malloc(size);
    if (!buf) {
        fdt_error("open %s err", file);
        fclose(fp);
        return qe_err_mem;
    }

    fread(buf, size, 1, fp);
    fclose(fp);

    *rp = buf;
    return qe_ok;
}

void fdt_node_dump(const void *fdt, int node)
{
    int prop_offset, len;
    const char *name, *prop;

    name = fdt_get_name(fdt, node, NULL);
    fdt_info("Node: %s", name);

    prop_offset = fdt_first_property_offset(fdt, node);
    while (prop_offset >= 0) {
        name = fdt_getprop_by_offset(fdt, prop_offset, &prop, &len);
        fdt_info("  Property: %s = %.*s", name, len, prop);
        prop_offset = fdt_next_property_offset(fdt, prop_offset);
    }

    node = fdt_first_subnode(fdt, node);
    while (node >= 0) {
        fdt_node_dump(fdt, node);
        node = fdt_next_subnode(fdt, node);
    }
}

static void fdt_compatible_match(void *fdt, char *match)
{
    int node, len;
    const char *name;
    const char *compatible;
    const uint32_t *reg;
    qe_u32 *reg_val;
    qe_u32 addr, size;

    node = fdt_node_offset_by_compatible(fdt, -1, match);
    if (node < 0) {
        fdt_error("fail to find node with compatible %s", match);
        return;
    }

    compatible = fdt_getprop(fdt, node, "compatible", NULL);
    fdt_info("Node with compatible %s found: %s", match, compatible);

    reg = fdt_getprop(fdt, node, "reg", &len);
    if (!reg || len < 4) {
        fdt_error("fail to get reg property");
        return;
    }

    reg_val = (qe_u32 *)reg;
    addr = fdt32_to_cpu(reg_val[0]);
    size = fdt32_to_cpu(reg_val[1]);
    fdt_info("reg address: 0x%x, size: 0x%x", addr, size);


    int nreg;
    int offset;

    offset = fdt_node_offset_by_compatible(fdt, -1, match);

    while (offset >= 0) {
        
        compatible = fdt_getprop(fdt, offset, "compatible", NULL);
        if (compatible == NULL) {
            fdt_error("compatible string not found");
            return;
        }
        
        reg = fdt_getprop(fdt, offset, "reg", &nreg);
        if (reg == NULL) {
            fdt_error("register values not found");
            return;
        }

        name = fdt_get_name(fdt, offset, &len);
        if (!name)
            return;

        fdt_info("node %s", name);
        fdt_info("compatible %s", compatible);

        reg_val = (qe_u32 *)reg;
        addr = fdt32_to_cpu(reg_val[0]);
        size = fdt32_to_cpu(reg_val[1]);
        fdt_info("reg address: 0x%x, size: 0x%x", addr, size);

        offset = fdt_node_offset_by_compatible(fdt, offset, match);
    }
}

static void usage(void)
{
    printf("\n");
    printf("fdt-test <cmd> <opt>\n");
    printf("  -h,?,--help                print help information\n");
    printf("  -f,--file <file>           input filepath\n");
    printf("  -d,--dump <level>          dump dtb info\n");
    printf("  -c,--comp <compatible>     match node with compatible\n");
    printf("\n");
}

int main(int argc, char *argv[])
{
    int opt;
    int mode = FDT_MODE_DUMP;
    char *filepath;
    char *compatible_match;
    qe_ret err;

    qelog_init(QELOG_DEBUG, QELOG_LV|QELOG_DM|QELOG_CL);

    static const struct option long_opts[] = {
        {"file", required_argument, QE_NULL, 'f'},
        {"comp", required_argument, QE_NULL, 'c'},
        {"dump", no_argument,       QE_NULL, 'd'},
        {"help", no_argument,       QE_NULL, 'h'},
    };

    while ((opt = getopt_long(argc, argv, "f:c:d?h-", long_opts, NULL)) != -1) {
        
        switch (opt) {

        case 'f':
            filepath = optarg;
            break;

        case 'c':
            mode = FDT_MODE_COMP;
            compatible_match = optarg;
            break;

        case 'd':
            mode = FDT_MODE_DUMP;
            break;

        case 'h':
        case '?':
        default:
            usage();
            exit(EXIT_SUCCESS);
        }
    }

    void *fdt;
    err = fdt_read(filepath, &fdt);
    if (err > 0) {
        fdt_error("open dtb %s err:%d", err);
        return -1;
    }

    int root = fdt_path_offset(fdt, "/");
    if (root < 0) {
        fdt_error("fail to find root");
        return -1;
    }

    switch (mode) {

    case FDT_MODE_COMP:
        fdt_compatible_match(fdt, compatible_match);
        break;

    case FDT_MODE_DUMP:
        fdt_node_dump(fdt, root);
        break;
    }

    return 0;
}